package com.ljf.framework.schedule;

import java.util.Calendar;
import java.util.Date;
import java.util.Set;
import java.util.TreeSet;

import static java.util.Calendar.DATE;
import static java.util.Calendar.DAY_OF_YEAR;

/**
 * 说明：
 *
 * @Auther: lijinfeng
 * @Date: 2025/1/9
 */
public class CronParser {
    private String cronExp;

    public CronParser(String exp) {
        this.cronExp = exp;
    }

    public Date nextDate(Date start) {
        Calendar lastCal = Calendar.getInstance();
        lastCal.setTime(start);

        //上一次执行时间字段
        int lastYear = lastCal.get(Calendar.YEAR);
        int lastMonth = lastCal.get(Calendar.MONTH) + 1;
        int lastDay = lastCal.get(Calendar.DAY_OF_MONTH);
        int lastMonthDay = this.encodeMonthday(lastMonth, lastDay);
        int lastHour = lastCal.get(Calendar.HOUR_OF_DAY);
        int lastMinute = lastCal.get(Calendar.MINUTE);
        int lastSecond = lastCal.get(Calendar.SECOND);

        //下一次执行时间字段
        Integer newMonthDay = null;
        Integer newHour = null;
        Integer newMinute = null;
        Integer newYear = lastYear;

        //解析cron表达式
        String[] exps = cronExp.split("\\s+");
        CronRange minute = parseRange(exps[0], 0, 59);
        CronRange hour = parseRange(exps[1], 0, 23);
        CronRange day = parseRange(exps[2], 1, 31);
        CronRange month = parseRange(exps[3], 1, 12);
        CronRange week = parseRange(exps[4], 1, 7);
        CronRange monthDay = this.calMonthDay(month, day, week);
        if (monthDay.isEmpty()) {
            return null;
        }

        boolean isNotFound = false;
        if (monthDay.inRange(lastMonthDay)) {
            if (hour.inRange(lastHour)) {
                if (minute.inRange(lastMinute)) {
                    newMinute = minute.getNextValue(lastMinute);
                }
                if (newMinute == null) {
                    //如果分钟找不到，需要对小时进行递增
                    newHour = hour.getNextValue(lastHour);
                    isNotFound = newHour == null;
                    newMinute = minute.getMin();
                } else {
                    newHour = lastHour;
                }
            }
            if (newHour == null) {
                if (isNotFound) {
                    //如果小时找不到，需要对天数进行递增
                    if (monthDay.isAll()) {
                        Calendar c = Calendar.getInstance();
                        c.setTime(start);
                        c.add(DATE, 1);
                        newMonthDay = this.encodeMonthday(c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH));
                    } else {
                        //如果跨年了就找不到
                        newMonthDay = monthDay.getNextValue(lastMonthDay);
                    }
                } else {
                    newMonthDay = lastMonthDay;
                }
                newHour = hour.getMin();
                newMinute = minute.getMin();
            } else {
                newMonthDay = lastMonthDay;
            }
        } else {
            //天如果不在范围内，需要对天进行递增
            newMonthDay = monthDay.getNextValue(lastMonthDay);
            newHour = hour.getMin();
            newMinute = minute.getMin();
        }
        if (newMonthDay == null) {
            //跨年
            newYear = newYear + 1;
            if (monthDay.isAll()) {
                //1月1日
                newMonthDay = 0x0101;
            } else {
                newMonthDay = monthDay.getMin();
            }
            newHour = hour.getMin();
            newMinute = minute.getMin();
        }
        Calendar newCal = Calendar.getInstance();
        newCal.set(Calendar.MONTH, this.decodeMonth(newMonthDay) - 1);
        newCal.set(Calendar.DAY_OF_MONTH, decodeDay(newMonthDay));
        newCal.set(Calendar.HOUR_OF_DAY, newHour);
        newCal.set(Calendar.MINUTE, newMinute);
        newCal.set(Calendar.SECOND, lastSecond);
        newCal.set(Calendar.YEAR, newYear);
        return newCal.getTime();
    }

    /**
     * 将月和日合并成一个int型整数
     * @param month
     * @param day
     * @return
     */
    public int encodeMonthday(int month, int day) {
        return (day & 0x000000FF) | (month << 8 & 0x0000FF00);
    }

    /**
     * 解码月
     * @param monthDay
     * @return
     */
    public int decodeMonth(int monthDay) {
        return (monthDay & 0x0000FF00) >> 8;
    }

    /**
     * 解码日
     * @param monthDay
     * @return
     */
    public int decodeDay(int monthDay) {
        return (monthDay & 0x000000FF);
    }

    private CronRange calMonthDay(CronRange month, CronRange day, CronRange week) {
        CronRange monthDay = new CronRange();
        if (month.isAll() && day.isAll() && week.isAll()) {
            //如果都是全范围的就不进行计算
            monthDay.setReturnAll(true);
            return monthDay;
        }
        int[] monthDays = {31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        //如果是闰年就是29天
        monthDays[1] = Calendar.getInstance().getActualMaximum(DAY_OF_YEAR) > 365 ? 29 : 28;
        Set<Integer> rangeMonth = month.getRange();
        for (Integer m : rangeMonth) {
            for (int d = 1; d <= monthDays[m - 1]; ++d) {
                if (day.inRange(d)) {
                    //判断周的逻辑
                    if (!week.isAll()) {
                        Calendar cal = Calendar.getInstance();
                        cal.set(Calendar.MONTH, m - 1);
                        cal.set(Calendar.DAY_OF_MONTH, d);
                        int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
                        //周日-周六==>1-7
                        w = w == 0 ? 7 : w;
                        if (!week.inRange(w)) {
                            continue;
                        }
                    }
                    monthDay.addRange(this.encodeMonthday(m, d));
                }
            }
        }
        return monthDay;
    }

    /**
     * 解析表达式的取值范围和循环周期
     *
     * @param exp
     * @param start
     * @param end
     * @return
     */
    public CronRange parseRange(String exp, int start, int end) {
        String[] exps = exp.trim().split("/");
        CronRange range = new CronRange();
        if (exps.length > 1) {
            range.setCycle(Integer.parseInt(exps[1]));
        }

        if (exps[0].trim().length() == 0) {
            range.range(start, end);
        } else if ("*".equals(exps[0])) {
            range.range(start, end);
            range.setReturnAll(exps.length == 1);
        } else if (exps[0].contains("-")) {
            String[] ss = exps[0].split("-");
            range.range(Integer.parseInt(ss[0]), Integer.parseInt(ss[1]));
        } else if (exps[0].contains(",")) {
            String[] ss = exps[0].split(",");
            for (String s : ss) {
                range.addRange(Integer.parseInt(s));
            }
        } else {
            range.addRange(Integer.parseInt(exps[0]));
        }
        return range;
    }
}

class CronRange {
    private Set<Integer> range = new TreeSet<>();
    private Integer cycle;
    private Integer max = null;
    private Integer min = null;
    private Boolean returnAll = false;

    public CronRange range(int start, int end) {
        for (int i = start; i <= end; ++i) {
            this.addRange(i);
        }
        return this;
    }

    public CronRange addRange(int value) {
        max = (max == null || value > max) ? value : max;
        min = (min == null || value < min) ? value : min;
        this.range.add(value);
        return this;
    }

    public Set<Integer> getRange() {
        return range;
    }


    public void setCycle(Integer cycle) {
        this.cycle = cycle;
    }


    public boolean inRange(int value) {
        return returnAll ? true : range.contains(value);
    }

    public boolean isEmpty() {
        return !returnAll && range.isEmpty();
    }


    public Integer getNextValue(int lastValue) {
        Integer value = null;
        if (this.cycle != null) {
            value = this.cycle + lastValue;
            while (!inRange(value)) {
                value = value + this.cycle;
                if (value > max) {
                    value = null;
                    break;
                }
            }
        } else {
            value = this.getNextMin(lastValue);
        }
        return value;
    }

    private Integer getNextMin(int value) {
        Integer[] integers = range.toArray(new Integer[range.size()]);
        Integer minValue = null;
        for (int i = 0; i < integers.length; ++i) {
            if (integers[i] > value) {
                minValue = integers[i];
                break;
            }
        }
        return minValue;
    }


    public Boolean isAll() {
        return returnAll;
    }

    public void setReturnAll(Boolean returnAll) {
        this.returnAll = returnAll;
    }

    public Integer getMin() {
        return min;
    }
}
